﻿using UnityEngine;
using System;
using System.Collections.Generic;

namespace MyMesh
{

    public static class MeshExtensions
    {

        public static Vector3 CenterPivot(this Mesh mesh)
        {
            //<summary>
            //Centers the pivot of the mesh and returns the offset the geometry had to be moved by
            //</summary>
            mesh.RecalculateBounds();
            Vector3 diff = mesh.bounds.center;
            mesh.MovePivot(diff);
            return diff;
        }

        public static void DoubleSharedVertices(this Mesh mesh, int[] indices = null)
        {
            //<summary>
            //Doubles all shared vertices so that no vertices are shared by multiple triangles
            //</summary>
            int[] sourceIndices = mesh.triangles;
            Vector3[] sourceVerts = mesh.vertices;
            Vector2[] sourceUVs = mesh.uv;
            Vector3[] sourceNormals = mesh.normals;

            int[] newIndices = new int[sourceIndices.Length];
            Vector3[] newVertices = new Vector3[sourceIndices.Length];
            Vector2[] newUVs = new Vector2[sourceIndices.Length];
            Vector3[] newNormals = new Vector3[sourceIndices.Length];

            // Create a unique vertex for every index in the original Mesh:
            for (int i = 0; i < sourceIndices.Length; i++)
            {
                newIndices[i] = i;
                newVertices[i] = sourceVerts[sourceIndices[i]];
                newUVs[i] = sourceUVs[sourceIndices[i]];
                newNormals[i] = sourceNormals[sourceIndices[i]];
            }

            mesh.vertices = newVertices;
            mesh.uv = newUVs;
            mesh.triangles = newIndices;
            mesh.normals = newNormals;
        }

        public static Mesh Duplicate(this Mesh mesh)
        {
            //<summary>
            //Returns a duplicate of the mesh that is passed in 
            //</summary>
            Mesh newMesh = new Mesh();
            if (mesh.name.EndsWith("(Copy)"))
            {
                newMesh.name = mesh.name;
            }
            else
            {
                newMesh.name = mesh.name + "(Copy)";
            }
            newMesh.vertices = mesh.vertices;
            newMesh.uv = mesh.uv;
            newMesh.triangles = mesh.triangles;
            newMesh.normals = mesh.normals;
            newMesh.colors = mesh.colors;
            newMesh.tangents = mesh.tangents;
            return newMesh;
        }

        public static void ExtrudeTrianglesByIndex(this Mesh mesh, int[] indices, float dist = 1.0f)
        {
            int[] tris = mesh.triangles;
            int[] tris2 = new int[tris.Length + 18 * indices.Length];
            Vector3[] normals = mesh.normals;
            Vector3[] verts = mesh.vertices;
            Vector2[] uvs = mesh.uv;
            int j = 0;
            for (int i = 0; i < tris.Length; i += 3)
            {
                if (Array.IndexOf(indices, i / 3) == -1)
                {
                    tris2[j++] = tris[i];
                    tris2[j++] = tris[i + 1];
                    tris2[j++] = tris[i + 2];
                }
                else
                {
                    Vector3 direction = Vector3.Cross(verts[tris[i + 1]] - verts[tris[i]], verts[tris[i + 2]] - verts[tris[i]]).normalized;
                    System.Array.Resize(ref verts, verts.Length + 3);
                    System.Array.Resize(ref normals, normals.Length + 3);
                    System.Array.Resize(ref uvs, uvs.Length + 3);
                    int p1index = verts.Length - 3;
                    int p2index = verts.Length - 2;
                    int p3index = verts.Length - 1;
                    verts[p1index] = verts[tris[i]] + dist * direction;
                    verts[p2index] = verts[tris[i + 1]] + dist * direction;
                    verts[p3index] = verts[tris[i + 2]] + dist * direction;
                    normals[p1index] = normals[tris[i]];
                    normals[p2index] = normals[tris[i + 1]];
                    normals[p3index] = normals[tris[i + 2]];
                    uvs[p1index] = uvs[tris[i]];
                    uvs[p2index] = uvs[tris[i + 1]];
                    uvs[p3index] = uvs[tris[i + 2]];

                    tris2[j++] = tris[i];
                    tris2[j++] = p2index;
                    tris2[j++] = p1index;

                    tris2[j++] = tris[i];
                    tris2[j++] = tris[i + 1];
                    tris2[j++] = p2index;

                    tris2[j++] = tris[i + 1];
                    tris2[j++] = p3index;
                    tris2[j++] = p2index;

                    tris2[j++] = tris[i + 1];
                    tris2[j++] = tris[i + 2];
                    tris2[j++] = p3index;

                    tris2[j++] = tris[i + 2];
                    tris2[j++] = p1index;
                    tris2[j++] = p3index;

                    tris2[j++] = tris[i + 2];
                    tris2[j++] = tris[i];
                    tris2[j++] = p1index;

                    tris2[j++] = p1index;
                    tris2[j++] = p2index;
                    tris2[j++] = p3index;
                }
            }
            mesh.vertices = verts;
            mesh.normals = normals;
            mesh.uv = uvs;
            mesh.triangles = tris2;
            mesh.RecalculateBounds();
        }

        public static void FlipNormals(this Mesh mesh, int[] indices = null)
        {
            //<summary>
            //Flips the normals of the mesh. Optional argument indices flips normals of only specified triangles
            //</summary>
            int[] tris = mesh.triangles;
            if (indices == null)
            {
                for (int i = 0; i < tris.Length; i += 3)
                {
                    int intermediate = tris[i];
                    tris[i] = tris[i + 2];
                    tris[i + 2] = intermediate;
                }
            }
            else
            {
                foreach (int i in indices)
                {
                    int intermediate = tris[i * 3];
                    tris[i * 3] = tris[i * 3 + 2];
                    tris[i * 3 + 2] = intermediate;
                }
            }
            mesh.triangles = tris;
        }

        public static void ImportData(this Mesh mesh, Mesh dataMesh)
        {
            //<summary>
            //Imports mesh data from another mesh
            //</summary>
            mesh.Clear();
            mesh.vertices = dataMesh.vertices;
            mesh.uv = dataMesh.uv;
            mesh.uv2 = dataMesh.uv2;
            mesh.uv2 = dataMesh.uv2;
            mesh.colors = dataMesh.colors;
            mesh.normals = dataMesh.normals;
            mesh.triangles = dataMesh.triangles;
            mesh.RecalculateBounds();
        }

        public static void MirrorAxis(this Mesh mesh, string axis)
        {
            //<summary>
            //Flips the vertices of the Mesh over the axis that was passed in
            //</summary>
            Vector3[] vertices = mesh.vertices;
            for (int i = 0; i < vertices.Length; i++)
            {
                if (axis == "x")
                {
                    vertices[i].x = -vertices[i].x;
                }
                else if (axis == "y")
                {
                    vertices[i].y = -vertices[i].y;
                }
                else if (axis == "z")
                {
                    vertices[i].z = -vertices[i].z;
                }
            }
            mesh.vertices = vertices;
            mesh.FlipNormals();
            mesh.RecalculateBounds();
        }

        public static void MovePivot(this Mesh mesh, Vector3 offset)
        {
            //<summary>
            //Moves the pivot of a mesh by given offset
            //</summary>
            Vector3[] vertices = mesh.vertices;
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i] = new Vector3(vertices[i].x - offset.x, vertices[i].y - offset.y, vertices[i].z - offset.z);
            }
            mesh.vertices = vertices;
            mesh.RecalculateBounds();
        }

        public static void MoveVertices(this Mesh mesh, Vector3 offset, int[] indices = null)
        {
            //<summary>
            //Moves the vertices of a mesh by given offset. The optional parameter
            //indices will move only specific vertices by the offset.
            //</summary>
            Vector3[] verts = mesh.vertices;
            for (int i = 0; i < verts.Length; i++)
            {
                if (indices == null || System.Array.Exists(indices, p => p == i))
                {
                    verts[i] += offset;
                }
            }
            mesh.vertices = verts;
            mesh.RecalculateBounds();
        }

        public static void RemoveDoubles(this Mesh mesh, float threshold = 0.001f)
        {
            Vector3[] verts = mesh.vertices;

            // Build new vertex buffer and remove "duplicate" verticies
            // that are within the given threshold.
            List<Vector3> newVerts = new List<Vector3>();
            List<Vector2> newUVs = new List<Vector2>();

            int k = 0;

            foreach (Vector3 vert in verts)
            {
                // Has vertex already been added to newVerts list?
                foreach (Vector3 newVert in newVerts)
                    if (Vector3.Distance(newVert, vert) <= threshold)
                        goto skipToNext;

                // Accept new vertex!
                newVerts.Add(vert);
                newUVs.Add(mesh.uv[k]);

                skipToNext:
                ;
                ++k;
            }

            // Rebuild triangles using new verticies
            int[] tris = mesh.triangles;
            for (int i = 0; i < tris.Length; ++i)
            {
                // Find new vertex point from buffer
                for (int j = 0; j < newVerts.Count; ++j)
                {
                    if (Vector3.Distance(newVerts[j], verts[tris[i]]) <= threshold)
                    {
                        tris[i] = j;
                        break;
                    }
                }
            }

            // Update mesh!
            mesh.Clear();
            mesh.vertices = newVerts.ToArray();
            mesh.triangles = tris;
            mesh.uv = newUVs.ToArray();
            mesh.RecalculateBounds();
        }

        public static void RemoveTriangles(this Mesh mesh, int[] indices = null)
        {
            //<summary>
            //Removes triangles at the indices passed in.
            //Note for advanced users: an index of 3 represents the 3rd complete triangle;
            //ie. triangles[9], triangles[10], triangles[11];
            //NOT triangles[3], triangles[4], triangles[5]
            //</summary>
            int[] tris = mesh.triangles;
            int[] tris2 = new int[tris.Length];
            int j = 0;
            int removedtris = 0;
            for (int i = 0; i < tris.Length; i += 3)
            {
                if (Array.IndexOf(indices, i / 3) == -1)
                {
                    tris2[j++] = tris[i];
                    tris2[j++] = tris[i + 1];
                    tris2[j++] = tris[i + 2];
                }
                else
                {
                    removedtris++;
                }
            }
            System.Array.Resize(ref tris2, tris2.Length - removedtris * 3);
            mesh.triangles = tris2;
        }
        /*
        public static void RemoveVertices(this Mesh mesh, int[] indices = null){
            //<summary>
            //removes vertices and all dependant data
            //optional argument indices can be used to remove specific indicies
            if (indices != null && indices.Length > 0) {
                List<int> tris = mesh.triangles.ToList();
                List<Vector3> verts = mesh.vertices.ToList();
                List<Vector2> uvs = mesh.uv;
                List<Vector3> normals = mesh.normals;
                //Remove dependant triangles
                List<int> triindices = new List<int> ();
                for (int i=0; i<tris.Count; i+=3) {
                    if(System.Array.Exists(indices, p => p==tris[i] || p==tris[i+1] || p==tris[i+2])){
                        triindices.Add (i / 3);
                    }
                }
                mesh.RemoveTriangles (triindices.ToArray ());
                //remove selected vertices
                int i = 0;
                int j = 0;
                while (i<verts.Count){
                    if(System.Array.Exists (indices, p => p==j)){
                        verts.RemoveAt(i);
                        normals.RemoveAt(i);
                        uvs.RemoveAt(i);
                    } else{
                        i++;
                    }
                    j++;
                }
                mesh.Clear ();
                mesh.vertices = verts.ToArray();
                mesh.normals = normals.ToArray();
                mesh.uv = uvs.ToArray();
                mesh.triangles = tris.ToArray();
            }
        }
        */

        public static void RemoveVertices(this Mesh mesh, int[] indices = null)
        {
            //<summary>
            //removes vertices and all dependant data
            //optional argument indices can be used to remove specific indicies
            //Load data
            if (indices != null && indices.Length > 0)
            {
                int[] tris = mesh.triangles;
                Vector3[] verts = mesh.vertices;
                Vector2[] uvs = mesh.uv;
                Vector2[] uv1s = mesh.uv2;
                Vector2[] uv2s = mesh.uv2;
                Vector3[] normals = mesh.normals;
                bool useUV1 = (mesh.uv2.Length > 0);
                bool useUV2 = (mesh.uv2.Length > 0);
                //remove dependant triangles
                List<int> triindices = new List<int>();
                for (int i = 0; i < tris.Length; i += 3)
                {
                    if (System.Array.Exists(indices, p => p == tris[i] || p == tris[i + 1] || p == tris[i + 2]))
                    {
                        triindices.Add(i / 3);
                    }
                }
                mesh.RemoveTriangles(triindices.ToArray());
                //remove selected vertices
                Vector3[] newverts = new Vector3[verts.Length - indices.Length];
                Vector2[] newuvs = new Vector2[verts.Length - indices.Length];
                Vector2[] newuv1s = new Vector2[verts.Length - indices.Length];
                Vector2[] newuv2s = new Vector2[verts.Length - indices.Length];
                Vector3[] newnormals = new Vector3[verts.Length - indices.Length];
                tris = mesh.triangles;
                int j = 0;
                int previousdeletions = 0;
                for (int i = 0; i < verts.Length; i += 1)
                {
                    if (!System.Array.Exists(indices, p => p == i))
                    {
                        newverts[j] = verts[i];
                        newuvs[j] = uvs[i];
                        if (useUV1) { newuv1s[j] = uv1s[i]; }
                        if (useUV2) { newuv2s[j] = uv2s[i]; }
                        newnormals[j] = normals[i];
                        j += 1;
                        if (previousdeletions > 0)
                        {
                            while (System.Array.Exists(tris, p => p == i))
                            {
                                tris[System.Array.FindIndex(tris, p => p == i)] -= previousdeletions;
                            }
                        }
                    }
                    else
                    {
                        previousdeletions += 1;
                    }
                }
                mesh.Clear();
                mesh.vertices = newverts;
                mesh.normals = newnormals;
                mesh.uv = newuvs;
                if (useUV1) { mesh.uv2 = newuv1s; }
                if (useUV2) { mesh.uv2 = newuv2s; }
                mesh.triangles = tris;
            }
        }

        public static void RotateUVs(this Mesh mesh, float rot)
        {
            Vector2[] uvs = mesh.uv;
            Quaternion eulerRot = Quaternion.Euler(0, 0, rot);
            for (var i = 0; i < uvs.Length; i++)
            {
                uvs[i] = eulerRot * (uvs[i] - new Vector2(0.5f, 0.5f)) + new Vector3(0.5f, 0.5f);
            }
            mesh.uv = uvs;
        }

        public static void SubdivideAndWeldTrianglesByEdge(this Mesh mesh, int[] indices, float threshold = 0.001f)
        {
            //<summary>
            //subdivides triangles by its edges
            //optional argument indices allows you to subdivide only specific triangles
            //</summary>
            int[] tris = mesh.triangles;
            int[] tris2 = new int[tris.Length + 9 * indices.Length];
            Vector3[] verts = mesh.vertices;
            Vector2[] uvs = mesh.uv;
            Vector3[] normals = mesh.normals;
            int j = 0;
            for (int i = 0; i < tris.Length; i += 3)
            {
                if (Array.IndexOf(indices, i / 3) == -1)
                {
                    tris2[j++] = tris[i];
                    tris2[j++] = tris[i + 1];
                    tris2[j++] = tris[i + 2];
                }
                else
                {
                    Vector3 vert1 = (verts[tris[i]] + verts[tris[i + 1]]) / 2;
                    Vector3 vert2 = (verts[tris[i + 1]] + verts[tris[i + 2]]) / 2;
                    Vector3 vert3 = (verts[tris[i + 2]] + verts[tris[i]]) / 2;
                    Vector2 uv1 = (uvs[tris[i]] + uvs[tris[i + 1]]) / 2;
                    Vector2 uv2 = (uvs[tris[i + 1]] + uvs[tris[i + 2]]) / 2;
                    Vector2 uv3 = (uvs[tris[i + 2]] + uvs[tris[i]]) / 2;
                    Vector3 normal1 = (normals[tris[i]] + normals[tris[i + 1]]) / 2;
                    Vector3 normal2 = (normals[tris[i + 1]] + normals[tris[i + 2]]) / 2;
                    Vector3 normal3 = (normals[tris[i + 2]] + normals[tris[i]]) / 2;
                    int p1index = System.Array.FindIndex(verts, p => Vector3.Distance(p, vert1) <= threshold);
                    int p2index = System.Array.FindIndex(verts, p => Vector3.Distance(p, vert2) <= threshold);
                    int p3index = System.Array.FindIndex(verts, p => Vector3.Distance(p, vert3) <= threshold);
                    if (p1index == -1)
                    {
                        p1index = verts.Length;
                        System.Array.Resize(ref verts, verts.Length + 1);
                        System.Array.Resize(ref uvs, uvs.Length + 1);
                        System.Array.Resize(ref normals, normals.Length + 1);
                        verts[p1index] = vert1;
                        uvs[p1index] = uv1;
                        normals[p1index] = normal1;
                    }
                    if (p2index == -1)
                    {
                        p2index = verts.Length;
                        System.Array.Resize(ref verts, verts.Length + 1);
                        System.Array.Resize(ref uvs, uvs.Length + 1);
                        System.Array.Resize(ref normals, normals.Length + 1);
                        verts[p2index] = vert2;
                        uvs[p2index] = uv2;
                        normals[p2index] = normal2;
                    }
                    if (p3index == -1)
                    {
                        p3index = verts.Length;
                        System.Array.Resize(ref verts, verts.Length + 1);
                        System.Array.Resize(ref uvs, uvs.Length + 1);
                        System.Array.Resize(ref normals, normals.Length + 1);
                        verts[p3index] = vert3;
                        uvs[p3index] = uv3;
                        normals[p3index] = normal3;
                    }

                    tris2[j++] = tris[i];
                    tris2[j++] = p1index;
                    tris2[j++] = p3index;

                    tris2[j++] = tris[i + 1];
                    tris2[j++] = p2index;
                    tris2[j++] = p1index;

                    tris2[j++] = tris[i + 2];
                    tris2[j++] = p3index;
                    tris2[j++] = p2index;

                    tris2[j++] = p1index;
                    tris2[j++] = p2index;
                    tris2[j++] = p3index;
                }
            }
            mesh.vertices = verts;
            mesh.triangles = tris2;
            mesh.uv = uvs;
            mesh.normals = normals;
            mesh.RecalculateBounds();
        }

        public static void SubdivideTrianglesByEdge(this Mesh mesh, int[] indices)
        {
            //<summary>
            //subdivides triangles by its edges
            //optional argument indices allows you to subdivide only specific triangles
            //</summary>
            int[] tris = mesh.triangles;
            int[] tris2 = new int[tris.Length + 9 * indices.Length];
            Vector3[] verts = mesh.vertices;
            Vector2[] uvs = mesh.uv;
            Vector3[] normals = mesh.normals;
            int j = 0;
            for (int i = 0; i < tris.Length; i += 3)
            {
                if (Array.IndexOf(indices, i / 3) == -1)
                {
                    tris2[j++] = tris[i];
                    tris2[j++] = tris[i + 1];
                    tris2[j++] = tris[i + 2];
                }
                else
                {
                    System.Array.Resize(ref verts, verts.Length + 3);
                    System.Array.Resize(ref uvs, uvs.Length + 3);
                    System.Array.Resize(ref normals, normals.Length + 3);
                    int p1index = verts.Length - 3;
                    int p2index = verts.Length - 2;
                    int p3index = verts.Length - 1;
                    verts[verts.Length - 3] = (verts[tris[i]] + verts[tris[i + 1]]) / 2;
                    verts[verts.Length - 2] = (verts[tris[i + 1]] + verts[tris[i + 2]]) / 2;
                    verts[verts.Length - 1] = (verts[tris[i + 2]] + verts[tris[i]]) / 2;

                    uvs[uvs.Length - 3] = (uvs[tris[i]] + uvs[tris[i + 1]]) / 2;
                    uvs[uvs.Length - 2] = (uvs[tris[i + 1]] + uvs[tris[i + 2]]) / 2;
                    uvs[uvs.Length - 1] = (uvs[tris[i + 2]] + uvs[tris[i]]) / 2;

                    normals[p1index] = (normals[tris[i]] + normals[tris[i + 1]]) / 2;
                    normals[p2index] = (normals[tris[i + 1]] + normals[tris[i + 2]]) / 2;
                    normals[p3index] = (normals[tris[i + 2]] + normals[tris[i]]) / 2;

                    tris2[j++] = tris[i];
                    tris2[j++] = p1index;
                    tris2[j++] = p3index;

                    tris2[j++] = tris[i + 1];
                    tris2[j++] = p2index;
                    tris2[j++] = p1index;

                    tris2[j++] = tris[i + 2];
                    tris2[j++] = p3index;
                    tris2[j++] = p2index;

                    tris2[j++] = p1index;
                    tris2[j++] = p2index;
                    tris2[j++] = p3index;
                }
            }
            mesh.vertices = verts;
            mesh.triangles = tris2;
            mesh.uv = uvs;
            mesh.normals = normals;
            mesh.RecalculateBounds();
        }

        public static void SubdivideTrianglesByCenter(this Mesh mesh, int[] indices = null)
        {
            //<summary>
            //subdivides triangles by its center
            //optional argument indices allows you to subdivide only specific triangles
            //</summary>
            int[] tris = mesh.triangles;
            int[] tris2 = new int[tris.Length + 6 * indices.Length];
            Vector3[] verts = mesh.vertices;
            Vector2[] uvs = mesh.uv;
            Vector3[] normals = mesh.normals;
            int j = 0;
            for (int i = 0; i < tris.Length; i += 3)
            {
                if (Array.IndexOf(indices, i / 3) == -1 && indices != null)
                {
                    tris2[j++] = tris[i];
                    tris2[j++] = tris[i + 1];
                    tris2[j++] = tris[i + 2];
                }
                else
                {
                    System.Array.Resize(ref verts, verts.Length + 1);
                    System.Array.Resize(ref uvs, uvs.Length + 1);
                    System.Array.Resize(ref normals, normals.Length + 1);
                    int newindex = verts.Length - 1;
                    verts[newindex] = (verts[tris[i]] + verts[tris[i + 1]] + verts[tris[i + 2]]) / 3;
                    uvs[newindex] = (uvs[tris[i]] + uvs[tris[i + 1]] + uvs[tris[i + 2]]) / 3;

                    normals[newindex] = (normals[tris[i]] + normals[tris[i + 1]] + normals[tris[i + 2]]) / 3;

                    tris2[j++] = tris[i];
                    tris2[j++] = tris[i + 1];
                    tris2[j++] = newindex;

                    tris2[j++] = tris[i + 1];
                    tris2[j++] = tris[i + 2];
                    tris2[j++] = newindex;

                    tris2[j++] = tris[i + 2];
                    tris2[j++] = tris[i];
                    tris2[j++] = newindex;
                }
            }
            mesh.vertices = verts;
            mesh.triangles = tris2;
            mesh.uv = uvs;
            mesh.normals = normals;
            mesh.RecalculateBounds();
        }

        public static void WeldVertices(this Mesh mesh, int[] indices)
        {
            //<summary>
            //Combines all vertices at indices into one vertex
            //</summary>

            //We can only merge if there are at least 2 vertices
            if (indices.Length > 1)
            {
                Vector3[] verts = mesh.vertices;
                Vector2[] uvs = mesh.uv;
                Vector3 averagePos = Vector3.zero;
                Vector2 averageUV = Vector2.zero;
                foreach (int index in indices)
                {
                    averagePos += verts[index];
                    averageUV += uvs[index];
                }
                averagePos = averagePos / indices.Length;
                averageUV = averageUV / indices.Length;
                foreach (int index in indices)
                {
                    verts[index] = averagePos;
                    uvs[index] = averageUV;
                }
                mesh.vertices = verts;
                mesh.uv = uvs;
                mesh.RecalculateBounds();
            }
        }
    }
}
